In this short course we will introduce some basic concepts of data oriented programming, both in R and Python.
A short list of the topics that will get covered is:
This concept will be discussed separaterly in the two lessons, one using R and the other using python.
All the concepts are applicable in both languages. One can use one language or the other, or a mixture or both.
This two concept are often mixed up. We could use this two informal descriptions:
Programming is about thinking how to formalize a certain sequence of operation.
Coding is writing a computer program that actually perfom the operations we have in mind.
While coding is strictly related to a specific programming language, but most of the programming skills are identical among different languages.
As a matter of fact, one does not even need a computer to be able to "program"
Try describing the steps necessary to prepare a coffee.
Even if the language is the same, the goal is completely different between the two.
Shell programming is useful for explotatory analysis, trying to understand what it's going on in your data
Script programming is useful to perform your analysis in a formal and reproducible way.
reproducibility is key
divide your program in meaningful, separated scripts.
each step should have its own script, that take inputs and gives output used by the following program
you need to repeat only the few last steps, not the whole thing, and can do that with the press of a button
Never, never, never, delete or modify the raw data!
When possible, choose text over binary format. Computer space is cheap, understanding the data is not
Metadata is the new gold
Make sure your data is always computer readable!
In the name of everything that is holy, never put the metadata as inplicit information. This includes:
If it is necessary to update the values with new informations, keep an update table on the side and incorporate it into the data cleaning process.
| table name | key value | variable name | note |
|---|---|---|---|
| phenotypical | John Black | weight | data is missing because the patient refused |
| biochemical | Jane Doe | cholesterol level | the vial was dropped and the sample lost |
| medical_history | Dave White | age of menarche | data is not applicable |
Your code to load and analyze the data is part of the metadata!
A report is nice, but it might be inaccurate.
The code describe exactly what has been done to the data.
Your environment configuration (OS version, R version, packages version) is part of the metadata as well.
Different versions of the same libraries could give different results!
sessionInfo()
R version 3.4.2 (2017-09-28) Platform: x86_64-pc-linux-gnu (64-bit) Running under: Ubuntu 17.10 Matrix products: default BLAS: /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3 LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so locale: [1] LC_CTYPE=it_IT.UTF-8 LC_NUMERIC=C [3] LC_TIME=it_IT.UTF-8 LC_COLLATE=it_IT.UTF-8 [5] LC_MONETARY=it_IT.UTF-8 LC_MESSAGES=it_IT.UTF-8 [7] LC_PAPER=it_IT.UTF-8 LC_NAME=C [9] LC_ADDRESS=C LC_TELEPHONE=C [11] LC_MEASUREMENT=it_IT.UTF-8 LC_IDENTIFICATION=C attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] bindrcpp_0.2 forcats_0.3.0 stringr_1.3.0 dplyr_0.7.4 [5] purrr_0.2.4 readr_1.1.1 tidyr_0.8.0 tibble_1.4.2 [9] ggplot2_2.2.1 tidyverse_1.2.1 loaded via a namespace (and not attached): [1] pbdZMQ_0.2-6 tidyselect_0.2.4 repr_0.12.0 [4] reshape2_1.4.3 haven_1.1.1 lattice_0.20-35 [7] colorspace_1.3-2 utf8_1.1.3 rlang_0.2.0 [10] pillar_1.2.1 foreign_0.8-69 glue_1.2.0 [13] modelr_0.1.1 readxl_1.0.0 uuid_0.1-2 [16] bindr_0.1.1 plyr_1.8.4 munsell_0.4.3 [19] gtable_0.2.0 cellranger_1.1.0 rvest_0.3.2 [22] psych_1.7.8 evaluate_0.10.1 labeling_0.3 [25] parallel_3.4.2 broom_0.4.3 IRdisplay_0.4.4 [28] Rcpp_0.12.16 scales_0.5.0 IRkernel_0.8.12.9000 [31] jsonlite_1.5 mnormt_1.5-5 hms_0.4.2 [34] digest_0.6.12 stringi_1.1.7 grid_3.4.2 [37] cli_1.0.0 tools_3.4.2 magrittr_1.5 [40] lazyeval_0.2.1 crayon_1.3.4 pkgconfig_2.0.1 [43] xml2_1.2.0 lubridate_1.7.3 assertthat_0.2.0 [46] httr_1.3.1 rstudioapi_0.7 R6_2.2.2 [49] nlme_3.1-131 compiler_3.4.2
packageVersion("tidyverse")
[1] ‘1.2.1’
Today we will discuss the basic of data exploratory analysis with R.
Wednesday we will discuss more data wrangling and model checking using Python.
Today I will show the similarities between python and R syntax for data exploration, and try to show some differencies.
There are really only 4 basic concepts to programming; anything else, in all languages, is basically just rehashing of these ideas.
do something only if a certain condition is met
x <- -5
if(x > 0) print("Non-negative number") else print("Negative number")
[1] "Negative number"
The if and else clauses can contain several statements, just be careful to keep the else on the same line as the closing brace
x <- 5
if(x > 0){
print("Positive number")
} else {
print("negative number")
}
[1] "Positive number"
x <- 5
y <- ifelse(x>0, 1, -1)
y
Python has a very similar look, but with less clutter:
x = 5
if x>0:
print("positive number")
else:
print("negative number")
y = 1 if x>0 else -1
From the language perspective of both python and R they are the same thing, but from a concept perspective, they are very different.
In both languages they are referred as functions, and can do both, but try to know which is which and plan accordingly
function_sign <- function(x_value){
sign <- ifelse(x_value>0, 1, -1)
return(sign)
}
function_sign(3)
in python, again, they are very similar
def function_sign(x_value):
sign = 1 if x_value>0 else -1
return sign
function_sign(-3)
R functions give some advanced functionalities, like partial recognition of the arguments.
Please, avoid relying on them, only evil can come out of it...
function_sign(x_value=-3) == function_sign(x=-3)
An inportant concept related to functions is the concept of scoping.
When we execute a function we can use variables that are reserved to the function.
This allow to write simpler code.
In R the scoping is the major difference between assigning with = and <-.
the easy solution is to always use <-
my_adder <- function(x){
g <- 3
return(x + y + g)
}
y = 4
print(my_adder(1))
[1] 8
print(g)
Error in print(g): oggetto "g" non trovato Traceback: 1. print(g)
DRY: Do not Repeat Yourself.
Repeating code (or copy pasting it) can lead to sever errors:
Functions are the primary way of implementing DRY principles.
The rule of thumb is that if you are going to do it more than twice, write a function instead... Sometime even if you are going to do it once!
x <- rexp(100, rate = 1)
x_med <- median(x)
x_min = min(x)
x_max = max(x)
x_top = x_max - x_med
x_bot = x_med - x_min
x_ratio = x_top/x_bot
x_ratio
This code is obscure and pollute the namespace!
interquantile_asymmetry <- function(data){
x_med <- median(data)
x_min = min(data)
x_max = max(data)
x_top = x_max - x_med
x_bot = x_med - x_min
x_ratio = x_top/x_bot
return(x_ratio)
}
interquantile_asymmetry(x)
Repeating an operation over and over, tipically with a different input each time, or with an exit condition.
Typically they underpin the usage of words like: while, for/for each, until.

from datacamp
years = c(2010, 2011, 2012)
for (year in years){
print(paste("The year is", year))
}
[1] "The year is 2010" [1] "The year is 2011" [1] "The year is 2012"
in python:
years = [2010, 2011, 2012]
for year in years:
print("The year is", year)
x <- 0
while (x<3){
print(paste("step", x))
x <- x+1
}
[1] "step 0" [1] "step 1" [1] "step 2"
in python
x = 0
while x<3:
print("step", x)
x = x+1
This are containers that holds the informations, and make more or less easy to retrieve and manipulate this information.
Programming can be described as creating and modifying data structures with the previous concept until the desired result is obtained.
There are a great number of data structures, and more get introduced in a language by libraries.
The most common and important one for our goals are tabular data.
Tabular data can be imagined as a single sheet of an Excel Spreadsheet, or a database table.
There are columns which describe different kinds of informations, and rows that contains the various informations for each observation (tuples)
in R they are implemented by the native data.frame or the more advanced tibble from dplyr.
In Python they are implemented as DataFrames from the pandas library
vectorization is the ability of a programming language to repeat a specific operation on all the elements of a data structure.
It is a form of inplicit iteration, with the possibility of inplicit conditionals.
It is an extremely important capability for data analysis, as it allow to express complex idea in an easy way
x <- c(1, 2, 3, 4)
x + 1
for (xi in x){
print(xi + 1)
}
[1] 2 [1] 3 [1] 4 [1] 5
x[x < 3] * 2
for (xi in x){
if (xi < 3) print(xi * 2)
}
[1] 2 [1] 4
the most common way one can obtain tabular data is by obtaining a csv or a tsv (comma separated values and tabular separated values), a text file encoding the table data explicitly.
My personal preference goes toward tsv, as they are more human readable:
a b c
170 M 1983
180 F 1972
compared to the equivalent csv:
a,b,c
170,M,1983
180,F,1972
For this course you should have been given few csv files:
notice that, even if the file name does not end with csv, they are perfectly valid anyway.
A csv is just a normal text file, formatted appropriatedly!
When in doubt, open it and check the formatting...
We have functions that read the data files and convert them directly into data.frames.
Never try do to this by hand, unless you are very certain of what you are doing, there are just too many pitfall
This is the first step to get your data in the right format.
This functions have several nice abilities, including automatic data type recognition: dates get recognized as dates, numbers as numbers.
When they fail, we need programming abilities to supplement their work.
UPLC_Plasma_Clinic <- read.csv("data/UPLC_Plasma_Clinic.txt",
header=TRUE, sep='\t', dec=',')
head(UPLC_Plasma_Clinic)
| Sample | Sex | Age | CaseControl |
|---|---|---|---|
| ID_0772 | male | 58.41205 | 0 |
| ID_0773 | female | 58.79535 | 0 |
| ID_0956 | female | 58.95414 | 0 |
| ID_0589 | male | 58.97057 | 0 |
| ID_0782 | female | 59.06913 | 0 |
| ID_0126 | male | 59.12936 | 1 |
the equivalent code in python would be:
import pandas as pd
UPLC_Plasma_Clinic = pd.read_csv("data/UPLC_Plasma_Clinic.txt",
sep='\t',
dec=',',
index_col='Sample')
UPLC_Plasma_Clinic.head()
Format as tidy data.
Tidy data are a sane way of representing you data in tabular form (correspond to the third normal form of database tables)
Each columns is a variable you want to observe, each row is an observation unit.
for R, the reference liset of libraries to work with tidy data is called tidyverse
library("tidyverse")
── Attaching packages ─────────────────────────────────────── tidyverse 1.2.1 ── ✔ ggplot2 2.2.1 ✔ purrr 0.2.4 ✔ tibble 1.4.2 ✔ dplyr 0.7.4 ✔ tidyr 0.8.0 ✔ stringr 1.3.0 ✔ readr 1.1.1 ✔ forcats 0.3.0 ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ── ✖ dplyr::filter() masks stats::filter() ✖ dplyr::lag() masks stats::lag()
There is a specific function for loading data using the tidyverse function read_csv (or read_tsv or read_delim).
They will load it into a tibble, a data structure similar to a basic R data.frame, but with some adjustment.
tibbles are a more modern structure, and when possible I suggest using them.
UPLC_Plasma_Clinic <- read_tsv("data/UPLC_Plasma_Clinic.txt", locale=locale(decimal_mark = ","))
head(UPLC_Plasma_Clinic)
Parsed with column specification: cols( Sample = col_character(), Sex = col_character(), Age = col_double(), CaseControl = col_integer() )
| Sample | Sex | Age | CaseControl |
|---|---|---|---|
| ID_0772 | male | 58.41205 | 0 |
| ID_0773 | female | 58.79535 | 0 |
| ID_0956 | female | 58.95414 | 0 |
| ID_0589 | male | 58.97057 | 0 |
| ID_0782 | female | 59.06913 | 0 |
| ID_0126 | male | 59.12936 | 1 |
There are various operations, defined as single verbs functions, that take the data.frame as input and send out another data.frame as output.
This operations can be linked as pipes (even if I personally would recommend against it)
filter).arrange).select).mutate).summarise).The first argument is a data.frame.
The subsequent arguments describe what to do with the data frame, using the variable names (without quotes).
The result is a new data frame, that can be passed to other functions in a similar fashion
partial_data <- filter(UPLC_Plasma_Clinic, CaseControl == 1, Sex=='male')
head(partial_data)
| Sample | Sex | Age | CaseControl |
|---|---|---|---|
| ID_0126 | male | 59.12936 | 1 |
| ID_0634 | male | 59.18686 | 1 |
| ID_0829 | male | 59.24435 | 1 |
| ID_0463 | male | 59.59206 | 1 |
| ID_1006 | male | 60.05476 | 1 |
| ID_0799 | male | 60.14784 | 1 |
in python there would be expressed as
table[table.column_name == some_value]
table[table['column_name'] == some_value]
or
table.query('column_name == some_value')
All these functions can be used in a functional way:
query = pd.DataFrame.query
query(table, 'column_name == some_value')
In general R manipulation functions looks more consistent than Python's one, as pandas's DataFrame try to keep a behavior consistent (where possible) with standard python.
beware of floating point numbers when doing comparisons!
sqrt(2) ^ 2 == 2
1/49 * 49 == 1
this is a problem of how computers represents numbers, not of R.
One would obtain the same results in R, Python, Matlab, C or most programming languages.
Could be solved with symbolic algebra, but it's a whole different topic (both R and Python support symbolic algebra witht external libraries).
In our case the data that we have is already in a tidy format.
at least apparently.
We should always check to make sure that the assumptions that we hold are correct.
This assumptions should be encoded in the script, so that they are maintained over time and these assumptions can ben checked again explicitly
nrow(UPLC_Plasma_Clinic)
length(UPLC_Plasma_Clinic)
stopifnot(length(UPLC_Plasma_Clinic)==4)
stopifnot(nrow(UPLC_Plasma_Clinic)==1235)
unique(UPLC_Plasma_Clinic$Sex)
stopifnot(length(unique(UPLC_Plasma_Clinic$Sex))==2)
stopifnot(min(UPLC_Plasma_Clinic$Age)>0)
unique(UPLC_Plasma_Clinic$CaseControl)
stopifnot(length(unique(UPLC_Plasma_Clinic$CaseControl))==2)
Visualizing data is extremely important because it can create a more intuitive understanding of what the data looks like.
the most common library used for visualization in R is called ggplot, an implementation of the concept described in the grammar of graphic review.
ggplot make it very easy to create and manipulate graphs in different ways
ggplot(data=UPLC_Plasma_Clinic, aes(Age)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
ggplot(data=UPLC_Plasma_Clinic, aes(Age)) + geom_histogram() + facet_grid(Sex ~ CaseControl)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
in python si potrebbe fare lo stesso con una notazione quasi identica con la libreria plotnine:
from plotnine import ggplot, geom_histogram, facet_grid, aes
ggplot(UPLC_Plasma_Clinic, aes('Age')) + geom_histogram() + facet_grid('Sex ~ CaseControl')
oppure usando seaborn, ma con una sintassi differente:
import seaborn as sbn
fg = sbn.FacetGrid(data=UPLC_Plasma_Clinic, row='CaseControl', col='Sex')
fg.map(plt.hist, "Age")
python is often a little more cluncky for the display, requiring more time to obtain a good looking one, but gives more power and control to the programmer to modify the graph as they seems more fit
now we load the data from the experimental design, detailing in which plate each experiment has been done, and want to check if there was an unbalance of gender or age in each plate.
UPLC_Plasma_ExpDes <- read.csv("data/UPLC_Plasma_ExpDes.txt",
header=TRUE, sep='\t', dec=',')
head(UPLC_Plasma_ExpDes)
| Sample | Plate | Column | Row |
|---|---|---|---|
| ID_0430 | 1 | 1 | A |
| ID_1159 | 1 | 1 | B |
| ID_0125 | 1 | 1 | C |
| ID_1142 | 1 | 1 | D |
| stand_38 | 1 | 1 | E |
| ID_0152_D | 1 | 1 | F |
nrow(UPLC_Plasma_ExpDes)
nrow(UPLC_Plasma_Clinic)
wait...
let's re-read the details of the data:
Replicated standards have "stand" label and duplicated samples have "_D" label
so, if we ignore the replicated, we should have the same index, right?
library(stringr)
partial_data = (UPLC_Plasma_ExpDes
%>% filter(!str_detect(Sample, 'stand'))
%>% filter(!str_detect(Sample, '_D')))
head(partial_data)
| Sample | Plate | Column | Row |
|---|---|---|---|
| ID_0430 | 1 | 1 | A |
| ID_1159 | 1 | 1 | B |
| ID_0125 | 1 | 1 | C |
| ID_1142 | 1 | 1 | D |
| ID_0913 | 1 | 1 | H |
| ID_0735 | 1 | 2 | B |
print(c(nrow(partial_data), nrow(UPLC_Plasma_Clinic)))
print(length(unique(UPLC_Plasma_Clinic$Sample)))
print(length(unique(partial_data$Sample)))
[1] 1251 1235 [1] 1235 [1] 1251
We need to start working with out transform to see what is going on.
There are two main families of transformations that one needs to understand:
grouping divide the dataset in sub dataset based on certain properties, apply an operation and merge the result together (such as calculating the average age for each Sex).
joining merge two different tables.
pivoting is used to transform something like a tidy table in a more squared table. The inverse operation is called melt.
The merging can be applied in several diffent ways, but the great four are:

from R for data science
there are other transformation that one can apply, such as creating new columns from old ones, removing or changing the values of various columns, but these don't require a detailed analysis.
anti_join(partial_data, UPLC_Plasma_Clinic, by='Sample')
Warning message: “Column `Sample` joining factor and character vector, coercing into character vector”
| Sample | Plate | Column | Row |
|---|---|---|---|
| ID_0553 | 3 | 1 | A |
| ID_1245 | 4 | 3 | G |
| ID_0750 | 4 | 10 | G |
| ID_0102 | 7 | 1 | G |
| ID_0527 | 8 | 8 | H |
| ID_0143 | 8 | 9 | D |
| ID_0511 | 9 | 4 | B |
| ID_0366 | 11 | 9 | A |
| ID_1239 | 12 | 4 | G |
| ID_1122 | 13 | 1 | H |
| ID_0474 | 13 | 3 | F |
| ID_0028 | 13 | 8 | D |
| ID_1000 | 13 | 10 | F |
| ID_0036 | 14 | 9 | B |
| ID_0450 | 15 | 6 | B |
| ID_0877 | 16 | 6 | F |
joined = left_join(UPLC_Plasma_Clinic, UPLC_Plasma_ExpDes, by='Sample')
head(joined)
Warning message: “Column `Sample` joining character vector and factor, coercing into character vector”
| Sample | Sex | Age | CaseControl | Plate | Column | Row |
|---|---|---|---|---|---|---|
| ID_0772 | male | 58.41205 | 0 | 9 | 2 | H |
| ID_0773 | female | 58.79535 | 0 | 10 | 11 | C |
| ID_0956 | female | 58.95414 | 0 | 13 | 9 | A |
| ID_0589 | male | 58.97057 | 0 | 1 | 9 | A |
| ID_0782 | female | 59.06913 | 0 | 2 | 1 | F |
| ID_0126 | male | 59.12936 | 1 | 13 | 8 | G |
grouped_by_gender_and_plate <- group_by(joined, Sex, Plate)
summarized = summarise(grouped_by_gender_and_plate,
Age_mean = mean(Age, na.rm = TRUE),
)
summarized
| Sex | Plate | Age_mean |
|---|---|---|
| female | 1 | 70.95811 |
| female | 2 | 68.70482 |
| female | 3 | 69.74278 |
| female | 4 | 70.91073 |
| female | 5 | 68.62026 |
| female | 6 | 69.47213 |
| female | 7 | 69.39157 |
| female | 8 | 66.33067 |
| female | 9 | 71.00442 |
| female | 10 | 71.52327 |
| female | 11 | 66.12275 |
| female | 12 | 67.71030 |
| female | 13 | 67.11625 |
| female | 14 | 68.46561 |
| female | 15 | 66.83220 |
| female | 16 | 68.78591 |
| male | 1 | 69.74392 |
| male | 2 | 69.91739 |
| male | 3 | 69.39781 |
| male | 4 | 69.70524 |
| male | 5 | 70.55061 |
| male | 6 | 70.43276 |
| male | 7 | 69.82132 |
| male | 8 | 70.62829 |
| male | 9 | 69.61314 |
| male | 10 | 69.81497 |
| male | 11 | 69.18442 |
| male | 12 | 69.83882 |
| male | 13 | 69.35556 |
| male | 14 | 69.14529 |
| male | 15 | 70.68414 |
| male | 16 | 68.94105 |
gender_age_per_cell = spread(summarized, key = Sex, value = Age_mean)
gender_age_per_cell
| Plate | female | male |
|---|---|---|
| 1 | 70.95811 | 69.74392 |
| 2 | 68.70482 | 69.91739 |
| 3 | 69.74278 | 69.39781 |
| 4 | 70.91073 | 69.70524 |
| 5 | 68.62026 | 70.55061 |
| 6 | 69.47213 | 70.43276 |
| 7 | 69.39157 | 69.82132 |
| 8 | 66.33067 | 70.62829 |
| 9 | 71.00442 | 69.61314 |
| 10 | 71.52327 | 69.81497 |
| 11 | 66.12275 | 69.18442 |
| 12 | 67.71030 | 69.83882 |
| 13 | 67.11625 | 69.35556 |
| 14 | 68.46561 | 69.14529 |
| 15 | 66.83220 | 70.68414 |
| 16 | 68.78591 | 68.94105 |
gender_age_per_cell['female'] - gender_age_per_cell['male']
| female |
|---|
| 1.2141950 |
| -1.2125726 |
| 0.3449651 |
| 1.2054852 |
| -1.9303516 |
| -0.9606325 |
| -0.4297402 |
| -4.2976181 |
| 1.3912761 |
| 1.7082968 |
| -3.0616759 |
| -2.1285176 |
| -2.2393106 |
| -0.6796886 |
| -3.8519422 |
| -0.1551436 |
to find out if it is significant, we have to verify if this difference is big or not compared to the uncertainties of the averages.
try to make a plot visualizing this!
geom_point e geom_errorbargrouped_by_gender_and_plate <- group_by(joined, Sex, Plate)
gender_age_per_cell = summarise(grouped_by_gender_and_plate,
Age_mean = mean(Age, na.rm = TRUE))
grouped_by_gender_and_plate <- group_by(joined, Sex, Plate)
gender_sem_per_cell = summarise(grouped_by_gender_and_plate,
Age_sem = sd(Age, na.rm = TRUE)/mean(Age, na.rm = TRUE))
joined_2 = inner_join(gender_age_per_cell, gender_sem_per_cell, by=c('Plate', 'Sex'), suffix=c('_mean', '_sem'))
head(joined_2)
| Sex | Plate | Age_mean | Age_sem |
|---|---|---|---|
| female | 1 | 70.95811 | 0.07673922 |
| female | 2 | 68.70482 | 0.09090547 |
| female | 3 | 69.74278 | 0.08719283 |
| female | 4 | 70.91073 | 0.09533404 |
| female | 5 | 68.62026 | 0.09598926 |
| female | 6 | 69.47213 | 0.08234907 |
(ggplot(joined_2, aes(x=Plate, y=Age_mean, color=Sex))
+ geom_point()
+ geom_errorbar(aes(ymin = Age_mean-2*Age_sem, ymax = Age_mean+2*Age_sem)))
grouped_by_gender_and_plate <- group_by(joined, Sex, Plate)
gender_age_per_cell = summarise(grouped_by_gender_and_plate,
Age_mean = mean(Age, na.rm = TRUE))
grouped_by_gender_and_plate <- group_by(joined, Sex, Plate)
gender_sem_per_cell = summarise(grouped_by_gender_and_plate,
Age_sem = sd(Age, na.rm = TRUE))
joined_3 = inner_join(gender_age_per_cell, gender_sem_per_cell, by=c('Plate', 'Sex'), suffix=c('_mean', '_sem'))
(ggplot(joined_3, aes(x=Plate, y=Age_mean, color=Sex))
+ geom_point()
+ geom_errorbar(aes(ymin = Age_mean-2*Age_sem, ymax = Age_mean+2*Age_sem)))
UPLC_Plasma_QC = read.csv("data/UPLC_Plasma_QC.txt", header=TRUE, sep='\t', dec=',')
head(UPLC_Plasma_QC)
| Sample | GP1 | GP2 | GP3 | GP4 | GP5 | GP6 | GP7 | GP8 | GP9 | ⋯ | GP29 | GP30 | GP31 | GP32 | GP33 | GP34 | GP35 | GP36 | GP37 | GP38 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ID_0001 | 10.165009 | 3.396722 | 0.14715726 | 4.510687 | 2.514603 | 1.513031 | 0.9389860 | 0.9981554 | 0.1481808 | ⋯ | 4.503079 | 0.4386959 | 0.8609633 | 1.604205 | 0.3349214 | 0.2236194 | 0.2483495 | 0.2793595 | 0.4867972 | 0.3077295 |
| ID_0002 | 6.346928 | 2.904260 | 0.07727235 | 4.300227 | 2.278086 | 1.881630 | 0.9796550 | 1.1736730 | 0.1035394 | ⋯ | 3.896761 | 0.4165660 | 0.9962668 | 2.116962 | 0.3009614 | 0.2976062 | 0.3464114 | 0.2330824 | 0.4800714 | 0.3791195 |
| ID_0003 | 5.574129 | 3.123896 | 0.19124440 | 4.033320 | 1.919623 | 1.652845 | 1.0088414 | 1.3790430 | 0.1541241 | ⋯ | 5.934060 | 0.4112420 | 1.4887753 | 1.700876 | 0.3187452 | 0.2103094 | 0.4033009 | 0.3863545 | 0.7684653 | 0.3900355 |
| ID_0004 | 5.821013 | 2.719029 | 0.07263451 | 2.799796 | 1.584999 | 1.719208 | 1.0109777 | 1.0781244 | 0.2231922 | ⋯ | 3.456380 | 0.2310796 | 1.3198401 | 4.657317 | 0.8959416 | 0.4555613 | 0.1711360 | 0.3067684 | 0.4479380 | 0.5426097 |
| ID_0005 | 5.086681 | 2.803290 | 0.08612441 | 3.835083 | 1.743258 | 2.047549 | 0.8771082 | 1.0652712 | 0.1246529 | ⋯ | 5.046724 | 0.6021798 | 1.2786875 | 1.961699 | 0.2950625 | 0.3155190 | 0.2923652 | 0.2557640 | 0.4909443 | 0.3186430 |
| ID_0006 | 6.168461 | 2.830691 | 0.15049772 | 5.539922 | 2.195944 | 1.930463 | 1.0652419 | 1.2603932 | 0.2017811 | ⋯ | 2.558369 | 0.3337653 | 1.0261129 | 2.389738 | 0.2741379 | 0.3554980 | 0.2601503 | 0.1387556 | 0.3757620 | 0.4172333 |
head(anti_join(UPLC_Plasma_QC, UPLC_Plasma_Clinic, by='Sample'))
Warning message: “Column `Sample` joining factor and character vector, coercing into character vector”
| Sample | GP1 | GP2 | GP3 | GP4 | GP5 | GP6 | GP7 | GP8 | GP9 | ⋯ | GP29 | GP30 | GP31 | GP32 | GP33 | GP34 | GP35 | GP36 | GP37 | GP38 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ID_0028 | 7.254032 | 2.301077 | 0.07822053 | 5.395068 | 3.110910 | 1.687864 | 0.9435919 | 1.0476941 | 0.11477590 | ⋯ | 3.042800 | 0.3128822 | 0.9130168 | 3.6482965 | 0.3765906 | 0.4713733 | 0.3009069 | 0.1618351 | 0.4398177 | 0.5028184 |
| ID_0036 | 8.284086 | 4.466458 | 0.29429094 | 4.471384 | 2.117016 | 2.250357 | 1.1215955 | 1.1134494 | 0.23104304 | ⋯ | 2.874674 | 0.2904410 | 1.5737015 | 2.9960200 | 0.3379810 | 0.3499346 | 0.2695834 | 0.1131894 | 0.3925807 | 0.4769936 |
| ID_0055_D | 4.221902 | 2.571742 | 0.04877766 | 2.252952 | 1.273124 | 1.740696 | 1.0365326 | 1.0280390 | 0.07380316 | ⋯ | 3.745615 | 0.3905159 | 2.3385107 | 4.3331589 | 0.5881124 | 0.6558892 | 0.3487720 | 0.1522003 | 0.4600204 | 0.7522964 |
| ID_0069_D | 11.511528 | 2.294800 | 0.05841700 | 5.996305 | 3.538727 | 1.603979 | 0.9098581 | 0.5440127 | 0.11046381 | ⋯ | 6.066910 | 0.4530151 | 1.0299588 | 0.4960067 | 0.2598982 | 0.1096066 | 0.2668001 | 0.4781896 | 0.7245645 | 0.2963088 |
| ID_0081_D | 3.284756 | 2.599404 | 0.10420373 | 3.094386 | 2.383987 | 2.342161 | 1.2708128 | 1.0007758 | 0.14767621 | ⋯ | 3.167308 | 0.3507045 | 1.1025397 | 3.2138767 | 0.3411178 | 0.4414385 | 0.4078650 | 0.2228715 | 0.5771981 | 0.6854249 |
| ID_0088_D | 6.923049 | 2.220780 | 0.07092579 | 5.897500 | 2.614751 | 2.072878 | 0.9222413 | 0.8909294 | 0.10884312 | ⋯ | 5.225910 | 0.5615480 | 1.1209052 | 1.8552607 | 0.3654608 | 0.2389141 | 0.2944541 | 0.3191557 | 0.5503988 | 0.2898597 |